Skip to main content

Crypto2FAHook Technical Documentation

Overview

Crypto2FAHook is a Solidity smart contract that implements a two-factor authentication (2FA) mechanism for blockchain wallets. It serves as a security hook that can be integrated with compatible wallet contracts to provide an additional layer of verification for transactions and signatures.

License

MIT

Solidity Version

^0.8.20

Dependencies

  • @soulwallet-core/contracts/interface/IHook.sol
  • @openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol
  • @openzeppelin/contracts/utils/cryptography/ECDSA.sol

Key Components

Constants

  • TIME_LOCK_DURATION: Set to 1 day (86400 seconds), defines the waiting period for 2FA address changes

Data Structures

User2FA Struct

struct User2FA {
bool initialized; // Whether the user has initialized 2FA
address wallet2FAAddr; // Current 2FA address
address pending2FAAddr; // Proposed new 2FA address (if change is pending)
uint256 effectiveTime; // Timestamp when pending change becomes effective
}

State Variables

  • mapping(address => User2FA) public user2FA: Maps user addresses to their 2FA configuration

Core Functionality

Interface Implementation

supportsInterface

function supportsInterface(bytes4 interfaceId) external pure returns (bool)

Implements ERC-165 interface detection.

  • Returns true if the contract implements IHook

Initialization

Init

function Init(bytes calldata data) external

Initializes 2FA for a wallet.

  • Extracts 2FA address from the first 20 bytes of data
  • Can only be called once per wallet
  • Throws if already initialized

DeInit

function DeInit() external

Removes 2FA configuration for a wallet.

  • Throws if not initialized
  • Deletes all user 2FA data

Validation Hooks

preIsValidSignatureHook

function preIsValidSignatureHook(bytes32 hash, bytes calldata hookSignature) external view

Validates signatures using 2FA.

  • Recovers signer address from hookSignature
  • Requires recovered address to match stored 2FA address

preUserOpValidationHook

function preUserOpValidationHook(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds,
bytes calldata hookSignature
) external view

Validates user operations using 2FA.

  • Recovers signer address from hookSignature
  • Requires recovered address to match stored 2FA address

2FA Management

initiateChange2FA

function initiateChange2FA(address new2FA) external

Initiates the process of changing the 2FA address.

  • Sets pending 2FA address
  • Starts time lock period

applyChange2FA

function applyChange2FA() external

Applies pending 2FA change after time lock expires.

  • Requires pending change and expired time lock
  • Updates 2FA address and clears pending state

cancelChange2FA

function cancelChange2FA() external

Cancels pending 2FA change before time lock expires.

  • Requires time lock not expired
  • Clears pending state

Technical Considerations

  1. Security

    • Uses OpenZeppelin's ECDSA for secure signature verification
    • Implements time lock for 2FA changes to prevent immediate unauthorized changes
    • Requires initialization before use
  2. Gas Optimization

    • Uses storage pointers for repeated access to the same user data
    • Implements view functions where possible to save gas
  3. Limitations

    • Cannot change 2FA address instantly
    • No recovery mechanism if 2FA address is lost

Integration Guide

To integrate this hook with a compatible wallet:

  1. Deploy the Crypto2FAHook contract
  2. Call Init with the desired 2FA address
  3. Ensure the wallet contract calls the appropriate hook functions:
    • preIsValidSignatureHook for signature validation
    • preUserOpValidationHook for user operation validation

Error Messages

  • "already initialized": Thrown when trying to initialize an already initialized user
  • "cannot deinit": Thrown when trying to de-initialize an uninitialized user
  • "Crypto2FAHook: invalid signature": Thrown when signature verification fails
  • "User not initialized": Thrown when uninitialized user attempts to change 2FA
  • "No pending change": Thrown when trying to apply non-existent 2FA change
  • "Time lock not expired": Thrown when trying to apply 2FA change too early
  • "Change already effective": Thrown when trying to cancel an expired change

Known Issues

There appear to be two syntax errors in the original code:

  1. *user2fa.effectiveTime = block.timestamp + TIME*LOCK_DURATION;
  2. *user2fa.wallet2FAAddr = *user2fa.pending2FAAddr;

These lines should be corrected to:

  1. _user2fa.effectiveTime = block.timestamp + TIME_LOCK_DURATION;
  2. _user2fa.wallet2FAAddr = _user2fa.pending2FAAddr;

Events

Note: The current implementation doesn't emit any events. Consider adding events for important state changes:

  • 2FA initialization
  • 2FA change initiation
  • 2FA change application
  • 2FA change cancellation